import generationMd5 from "../../lib/generationMd5";
import {initTask, preSignUrl, taskInfo} from './sysFileInfo';
import {ElNotification} from "element-plus";
import Queue from 'promise-queue-plus';
import axios from 'axios'
import {DEFAULT_SIZE} from "@/const/Const"

export class Task {

    /**
     * 上传的文件对象
     */
    file;

    /**
     * 任务记录，包含分片信息等
     */
    taskRecord;

    /**
     * 文件上传队列
     */
    fileUploadChunkQueue;

    /**
     * 上次断点续传时上传的总大小
     */
    lastUploadedSize = 0;

    /**
     * 已上传的大小
     */
    uploadedSize = 0;

    /**
     * 开始上传的时间
     */
    startMs= new Date().getTime();

    /**
     *
     * 是否暂停
     */
    isPaused = false;

    /**
     * 是否取消
     */
    isCancelled=false;

    /**
     * 记录已处理的断点分片
     */
    processedExitParts=new Set();

    /**
     * 失败的任务列表
     */
    failTasks=[];

    /**
     * 进度回调
     */
    processCallback;

    /**
     * 当前上传进度百分比
     */
    percent;

    /**
     *
     * @param file 上传的文件对象
     * @param taskRecord 任务记录，包含分片信息等
     * @param fileUploadChunkQueue 文件上传队列
     */
    constructor(file, taskRecord, fileUploadChunkQueue) {
        this.file = file;
        this.taskRecord = taskRecord;
        this.fileUploadChunkQueue = fileUploadChunkQueue;
    }

    /**
     * 暂停上传
     */
    pause() {
        this.isPaused = true;
        // 停止队列处理新任务
        if (this.fileUploadChunkQueue[this.file.uid]) {
            this.fileUploadChunkQueue[this.file.uid].stop();
        }
    }

    /**
     * 继续上传
     */
    resume() {
        if (this.isCancelled) {
            return Promise.reject('任务已被取消，无法继续');
        }
        if (!this.isPaused) {
            return Promise.reject('任务未暂停，无需继续');
        }
        this.isPaused = false;
        return this.handleUpload(); // 重新启动上传流程
    }

    /**
     * 取消上传
     */
    cancel() {
        // 停止队列并清空
        if (this.fileUploadChunkQueue[this.file.uid]) {
            this.fileUploadChunkQueue[this.file.uid].stop();
            this.fileUploadChunkQueue[this.file.uid] = undefined
        }

    }

    /**
     * 处理文件上传
     */
    handleUpload() {

        let {exitPartList, chunkNum} = this.taskRecord

        return new Promise(resolve => {

            let queue = this.fileUploadChunkQueue[this.file.uid]
            if (!queue) {
                // 初始化队列，设置并发数为5，重试次数为3
                queue = Queue(5, {
                    "retry": 3,               // 重试次数
                    "retryIsJump": false,     // 是否立即重试
                    "workReject": (reason, queue) => {
                        this.failTasks.push(reason)// 记录失败任务
                    },
                    "queueEnd": queue => {
                        resolve(this.failTasks);// 队列结束时返回失败任务列表
                    }
                })
                this.fileUploadChunkQueue[this.file.uid] = queue
            }

            exitPartList = exitPartList || []
            let queueCount = chunkNum - exitPartList.length
            for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {

                const exitPart = exitPartList.find(exitPart => exitPart.partNumber === partNumber)
                if (exitPart) {
                    // 避免重复处理已记录的分片
                    if (!this.processedExitParts.has(partNumber)) {
                        let exitPartSize = Number(exitPart.size);
                        // 分片已上传完成，累计到上传完成的总额中,同时记录一下上次断点上传的大小，用于计算上传速度
                        this.lastUploadedSize += exitPartSize
                        this.updateProcess(exitPartSize)
                        this.processedExitParts.add(partNumber);
                    }
                } else if (queueCount !== queue.getLength()) {
                    queue.push(() => this.uploadNext(partNumber).then(res => {
                        this.taskRecord.exitPartList = this.taskRecord.exitPartList || []
                        // 上传成功后记录到断点列表
                        this.taskRecord.exitPartList.push({
                            partNumber: res.partNumber,
                            size: res.uploadedSize
                        });
                        // 单片文件上传完成再更新上传进度
                        this.updateProcess(res.uploadedSize)
                        this.processedExitParts.add(res.partNumber);
                    }))
                }
            }
            if (queue.getLength() === 0) {
                // 所有分片都上传完，但未合并，直接return出去，进行合并操作
                resolve(this.failTasks);
                return;
            }
            queue.start()
        })
    }

    /**
     * 计算上传速度（byte/s）
     */
    getSpeed() {
        // 已上传的总大小 - 上次上传的总大小（断点续传）= 本次上传的总大小（byte）
        const intervalSize = this.uploadedSize - this.lastUploadedSize
        const nowMs = new Date().getTime()
        // 时间间隔（s）
        const intervalTime = (nowMs - this.startMs) / 1000
        return intervalSize / intervalTime
    }

    /**
     * 上传单个分片
     * @param partNumber 分片编号
     */
    async uploadNext(partNumber) {
        const {chunkSize, md5Code} = this.taskRecord
        const start = Number(chunkSize) * (partNumber - 1)
        const end = start + Number(chunkSize)
        const blob = this.file.slice(start, end)

        // 获取预签名URL
        const {code, data, msg} = await preSignUrl({md5: md5Code, partNumber: partNumber})
        if (code === 200000 && data) {
            await axios.request({
                url: data,
                method: 'PUT',
                data: blob,
                headers: {'Content-Type': 'application/octet-stream'}
            })
            return Promise.resolve({partNumber: partNumber, uploadedSize: blob.size})
        }
        return Promise.reject(`分片${partNumber}， 获取上传地址失败`)
    }

    /**
     * 更新上传进度
     * @param increment 本次上传的字节量
     */
    updateProcess(increment) {
        let totalSize = this.file.size;
        let factor = 1000; // 每次增加1000 byte
        let from = 0;
        // 通过循环逐步增加进度
        while (from <= increment) {
            from += factor
            this.uploadedSize += factor
            this.percent = Math.round(this.uploadedSize / totalSize * 100).toFixed(2);
        }

        const speed = this.getSpeed();
        const remainingTime = speed !== 0 ? Math.ceil((totalSize - this.uploadedSize) / speed) + 's' : '未知'

        if (this.processCallback) {
            this.processCallback(speed, remainingTime, this.uploadedSize)
        }
        console.log('剩余大小：', (totalSize - this.uploadedSize) / 1024 / 1024, 'mb');
        console.log('当前速度：', (speed / 1024 / 1024).toFixed(2), 'mbps');
        console.log('预计完成：', remainingTime);
    }
}


/*
 * 获取一个上传任务，没有则初始化一个
 */
export const getTaskInfo = async (file, md5) => {
    let task;
    const {code, data, msg} = await taskInfo(md5)
    if (code === 200000) {
        task = data
        if (!task) {
            const initTaskData = {
                md5,
                fileName: file.name,
                totalSize: file.size,
                chunkSize: DEFAULT_SIZE
            }
            const {code, data, msg} = await initTask(initTaskData)
            if (code === 200000) {
                task = data
            } else {
                ElNotification.error({
                    title: '文件上传错误',
                    message: msg
                })
            }
        }
    } else {
        ElNotification.error({
            title: '文件上传错误',
            message: msg
        })
    }
    return task
}
